package com.swak.consistency.core.proxy;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.util.Assert;

import com.swak.asm.ClassGenerator;
import com.swak.asm.ClassGenerator.Dc;
import com.swak.utils.ClassHelper;
import com.swak.utils.Lists;
import com.swak.utils.Methods;
import com.swak.utils.ReflectUtils;
import com.swak.utils.StringUtils;

/**
 * 代理工厂
 *
 * @author: lifeng
 * @date: 2020/3/29 20:32
 */
public class ProxyFactory {

	private static final AtomicLong PROXY_CLASS_COUNTER = new AtomicLong(0);
	private static final Map<ClassLoader, Map<Class<?>, Object>> PROXY_CACHE_MAP = new WeakHashMap<>();

	/**
	 * 创建代理类
	 * 
	 * @param filed
	 * @param service
	 * @return
	 */
	public static Object newProxy(Field filed, Object service) throws Exception {
		Assert.isTrue(filed != null, "type can Not Be Null");
		Assert.isTrue(filed.getType().isInterface(), "type Can Not Be Null");

		Class<?> type = filed.getType();
		return newProxy(ClassHelper.getClassLoader(ProxyFactory.class), type).getConstructor(Field.class, type)
				.newInstance(filed, service);
	}

	/**
	 * 创建代理类
	 * 
	 * @param type
	 * @param template
	 * @return
	 */
	private static Class<?> newProxy(ClassLoader cl, Class<?> type) {
		// get cache by class loader.
		final Map<Class<?>, Object> cache;
		synchronized (PROXY_CACHE_MAP) {
			cache = PROXY_CACHE_MAP.computeIfAbsent(cl, k -> new HashMap<>());
		}
		Class<?> proxy = null;
		synchronized (cache) {
			do {
				Object value = cache.get(type);
				if (value instanceof Reference<?>) {
					proxy = (Class<?>) ((Reference<?>) value).get();
					if (proxy != null) {
						return proxy;
					}
				}

				if (value == Object.class) {
					try {
						cache.wait();
					} catch (InterruptedException e) {
					}
				} else {
					cache.put(type, Object.class);
					break;
				}
			} while (true);
		}

		long id = PROXY_CLASS_COUNTER.getAndIncrement();
		ClassGenerator ccp = null;
		try {
			String ccp_name = type.getName() + "$$ConsistencyProxy" + id;
			ccp = ClassGenerator.newInstance(cl);
			ccp.setClassName(ccp_name);
			ccp.setSuperClass(AbstractConsistencyProxy.class);
			ccp.addInterface(type);

			List<String> signatures = Lists.newArrayList();
			signatures.add("L" + AbstractConsistencyProxy.class.getName() + "<L" + type.getName() + ";>");
			signatures.add("L" + type.getName());
			signatures.add("L" + Dc.class.getName());
			String signature = StringUtils.replace(StringUtils.join(signatures, ";"), ".", "/") + ";";
			ccp.setGenericSignature(signature);

			// 自定义的方法
			Method[] methods = type.getDeclaredMethods();
			if (methods != null && methods.length > 0) {
				for (int i = 0; i < methods.length; i++) {
					Method method = methods[i];
					if (!Methods.canProxy(method)) {
						continue;
					}

					Class<?> rt = method.getReturnType();
					Class<?>[] pts = method.getParameterTypes();

					StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length)
							.append("];");
					for (int j = 0; j < pts.length; j++) {
						code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
					}
					
					if (!Void.TYPE.equals(rt)) {
						code.append(" Object ret = ");
					}

					code.append(" this.invoke(_methods[").append(i).append("], args);");

					if (!Void.TYPE.equals(rt)) {
						code.append(" return ").append(asArgument(rt, "ret")).append(";");
					}
					ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(),
							code.toString());
				}

				ccp.addField("public static java.lang.reflect.Method[] _methods;");
			}

			ccp.addConstructor(Modifier.PUBLIC, new Class<?>[] { Field.class, type }, new Class<?>[0],
					"super($1, $2);");
			Class<?> clazz = ccp.toClass();
			clazz.getField("_methods").set(null, methods);

			proxy = clazz;
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			// release ClassGenerator
			if (ccp != null) {
				ccp.release();
			}
			synchronized (cache) {
				if (proxy == null) {
					cache.remove(type);
				} else {
					cache.put(type, new WeakReference<Class<?>>(proxy));
				}
				cache.notifyAll();
			}
		}
		return proxy;
	}

	private static String asArgument(Class<?> cl, String name) {
		if (cl.isPrimitive()) {
			if (Boolean.TYPE == cl) {
				return name + "==null?false:((Boolean)" + name + ").booleanValue()";
			}
			if (Byte.TYPE == cl) {
				return name + "==null?(byte)0:((Byte)" + name + ").byteValue()";
			}
			if (Character.TYPE == cl) {
				return name + "==null?(char)0:((Character)" + name + ").charValue()";
			}
			if (Double.TYPE == cl) {
				return name + "==null?(double)0:((Double)" + name + ").doubleValue()";
			}
			if (Float.TYPE == cl) {
				return name + "==null?(float)0:((Float)" + name + ").floatValue()";
			}
			if (Integer.TYPE == cl) {
				return name + "==null?(int)0:((Integer)" + name + ").intValue()";
			}
			if (Long.TYPE == cl) {
				return name + "==null?(long)0:((Long)" + name + ").longValue()";
			}
			if (Short.TYPE == cl) {
				return name + "==null?(short)0:((Short)" + name + ").shortValue()";
			}
			throw new RuntimeException(name + " is unknown primitive type.");
		}
		return "(" + ReflectUtils.getName(cl) + ")" + name;
	}
}
